Security News
CISA Brings KEV Data to GitHub
CISA's KEV data is now on GitHub, offering easier access, API integration, commit history tracking, and automated updates for security teams and researchers.
query-selector-shadow-dom
Advanced tools
use querySelector syntax to search for nodes inside of (nested) shadow roots
The query-selector-shadow-dom npm package allows you to query and interact with elements within the shadow DOM using standard CSS selectors. This is particularly useful for testing and automation tasks where you need to access elements encapsulated within shadow roots.
Querying elements within shadow DOM
This feature allows you to query elements within the shadow DOM using standard CSS selectors. The `querySelectorAllDeep` function searches through all shadow roots and returns matching elements.
const { querySelectorAllDeep } = require('query-selector-shadow-dom');
const elements = querySelectorAllDeep('my-element');
console.log(elements);
Querying a single element within shadow DOM
This feature allows you to query a single element within the shadow DOM. The `querySelectorDeep` function searches through all shadow roots and returns the first matching element.
const { querySelectorDeep } = require('query-selector-shadow-dom');
const element = querySelectorDeep('my-element');
console.log(element);
Customizing shadow DOM query
This feature allows you to customize the shadow DOM query. The `querySelectorAllDeep` function can take additional options to refine the search, such as including or excluding shadow DOM.
const { querySelectorAllDeep } = require('query-selector-shadow-dom');
const elements = querySelectorAllDeep('my-element', { includeShadowDom: true });
console.log(elements);
The shadow-dom-utils package provides utility functions for working with shadow DOM, including querying and manipulating shadow DOM elements. It offers similar functionalities to query-selector-shadow-dom but with additional utilities for shadow DOM manipulation.
The shadow-query package allows you to query elements within the shadow DOM using CSS selectors. It is similar to query-selector-shadow-dom but focuses solely on querying without additional customization options.
querySelector that can pierce Shadow DOM roots without knowing the path through nested shadow roots. Useful for automated testing of Web Components e.g. with Selenium, Puppeteer.
// available as an ES6 module for importing in Browser environments
import {
querySelectorAllDeep,
querySelectorDeep,
} from "query-selector-shadow-dom";
You can see that .dropdown-item:not([hidden])
(Open downloads folder) is several layers deep in shadow roots, most tools will make you do something like
document
.querySelector("body > downloads-manager")
.shadowRoot.querySelector("#toolbar")
.shadowRoot.querySelector(".dropdown-item:not([hidden])");
EW!
with query-selector-shadow-dom:
import {
querySelectorAllDeep,
querySelectorDeep,
} from "query-selector-shadow-dom";
querySelectorDeep(".dropdown-item:not([hidden])");
querySelectorAll
from the browser, will return an Array
of elements matching the queryquerySelector
from the browser, will return the first
matching element of the query.Both of the methods above accept a 2nd parameter, see section Provide alternative node
. This will change the starting element to search from i.e. it will find ancestors of that node that match the query.
querySelectorAll
functionality. You can read more about this here: https://github.com/Georgegriff/query-selector-shadow-dom/issues/54This plugin implements a custom selector strategy: https://webdriver.io/docs/selectors.html#custom-selector-strategies
// make sure you have selenium standalone running
const { remote } = require("webdriverio");
const {
locatorStrategy,
} = require("query-selector-shadow-dom/plugins/webdriverio");
(async () => {
const browser = await remote({
logLevel: "error",
path: "/wd/hub",
capabilities: {
browserName: "chrome",
},
});
// The magic - registry custom strategy
browser.addLocatorStrategy("shadow", locatorStrategy);
// now you have a `shadow` custom locator.
// All elements on the page
await browser.waitUntil(() =>
browser.custom$("shadow", ".btn-in-shadow-dom")
);
const elements = await browser.$$("*");
const elementsShadow = await browser.custom$$("shadow", "*");
console.log("All Elements on Page Excluding Shadow Dom", elements.length);
console.log(
"All Elements on Page Including Shadow Dom",
elementsShadow.length
);
await browser.url("http://127.0.0.1:5500/test/");
// find input element in shadow dom
const input = await browser.custom$("shadow", "#type-to-input");
// type to input ! Does not work in firefox, see above.
await input.setValue("Typed text to input");
// Firefox workaround
// await browser.execute((input, val) => input.value = val, input, 'Typed text to input')
await browser.deleteSession();
})().catch((e) => console.error(e));
shadow$
shadow$
only goes one level deep in a shadow root.
Take this example.
You can see that .dropdown-item:not([hidden])
(Open downloads folder) is several layers deep in shadow roots, but this library will find it, shadow$
would not.
You would have to construct a path via css or javascript all the way through to find the right element.
const { remote } = require("webdriverio");
const {
locatorStrategy,
} = require("query-selector-shadow-dom/plugins/webdriverio");
(async () => {
const browser = await remote({ capabilities: { browserName: "chrome" } });
browser.addLocatorStrategy("shadow", locatorStrategy);
await browser.url("chrome://downloads");
const moreActions = await browser.custom$("shadow", "#moreActions");
await moreActions.click();
const span = await browser.custom$("shadow", ".dropdown-item:not([hidden])");
const text = await span.getText();
// prints `Open downloads folder`
console.log(text);
await browser.deleteSession();
})().catch((e) => console.error(e));
https://webdriver.io/blog/2019/02/22/shadow-dom-support.html#browser-support
From the above, firefox setValue
does NOT currently work.
. A workaround for now is to use a custom command (or method on your component object) that sets the input field's value via browser.execute(function).
Safari pretty much doesn't work, not really a surprise.
There are some webdriver examples available in the examples folder of this repository. WebdriverIO examples
Update: As of 5.4.0 Puppeteer now has a built in shadow Dom selector, this module might not be required for Puppeteer anymore. They don't have any documentation: https://github.com/puppeteer/puppeteer/pull/6509
There are some puppeteer examples available in the examples folder of this repository.
Update: as of Playwright v0.14.0 their CSS and text selectors work with shadow Dom out of the box, you don't need this library anymore for Playwright.
Playwright works really nicely with this package.
This module exposes a playwright selectorEngine
: https://github.com/microsoft/playwright/blob/main/docs/api.md#selectorsregisterenginefunction-args
const { selectorEngine } = require("query-selector-shadow-dom/plugins/playwright");
const playwright = require('playwright');
await selectors.register('shadow', createTagNameEngine);
...
await page.goto('chrome://downloads');
// shadow= allows a css query selector that automatically pierces shadow roots.
await page.waitForSelector('shadow=#no-downloads span', {timeout: 3000})
For a full example see: https://github.com/Georgegriff/query-selector-shadow-dom/blob/main/examples/playwright
This project provides a Protractor plugin, which can be enabled in your protractor.conf.js
file:
exports.config = {
plugins: [
{
package: "query-selector-shadow-dom/plugins/protractor",
},
],
// ... other Protractor-specific config
};
The plugin registers a new locator - by.shadowDomCss(selector /* string */)
, which can be used in regular Protractor tests:
element(by.shadowDomCss("#item-in-shadow-dom"));
The locator also works with Serenity/JS tests that use Protractor under the hood:
import "query-selector-shadow-dom/plugins/protractor";
import { Target } from "@serenity-js/protractor";
import { by } from "protractor";
const ElementOfInterest = Target.the("element of interest").located(
by.shadowDomCss("#item-in-shadow-dom")
);
See the end-to-end tests for more examples.
// query from another node
querySelectorShadowDom.querySelectorAllDeep(
"child",
document.querySelector("#startNode")
);
// query an iframe
querySelectorShadowDom.querySelectorAllDeep("child", iframe.contentDocument);
This library does not allow you to query across iframe boundaries, you will need to get a reference to the iframe you want to interact with.
If your iframe is inside of a shadow root you could cuse querySelectorDeep
to find the iframe, then pass the contentDocument
into the 2nd argument of querySelectorDeep
or querySelectorAllDeep
.
In the below examples the components being searched for are nested within web components shadowRoots
.
// Download and Paste the lib code in dist into chrome://downloads console to try it out :)
console.log(
querySelectorShadowDom.querySelectorAllDeep(
"downloads-item:nth-child(4) #remove"
)
);
console.log(
querySelectorShadowDom.querySelectorAllDeep(
'#downloads-list .is-active a[href^="https://"]'
)
);
console.log(
querySelectorShadowDom.querySelectorDeep("#downloads-list div#title-area + a")
);
If using the polyfills and shady DOM, this library will still work.
window.querySelectorShadowDom
available for easy include into a test frameworknpm install
npm test
npm run watch
npm run build
FAQs
use querySelector syntax to search for nodes inside of (nested) shadow roots
The npm package query-selector-shadow-dom receives a total of 425,039 weekly downloads. As such, query-selector-shadow-dom popularity was classified as popular.
We found that query-selector-shadow-dom demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
CISA's KEV data is now on GitHub, offering easier access, API integration, commit history tracking, and automated updates for security teams and researchers.
Security News
Opengrep forks Semgrep to preserve open source SAST in response to controversial licensing changes.
Security News
Critics call the Node.js EOL CVE a misuse of the system, sparking debate over CVE standards and the growing noise in vulnerability databases.